﻿using Awesomium.Core;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Xml.Linq;
using System.Xml.Serialization;

#region Targets (Goals)
// 1. Customizing
// 2. Scraping
// 3. Uniforming
// 4. Site uploading
#endregion

namespace Huknow
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        //private WebClient webClient = new WebClient();
        //private ConcurrentQueue<SharpPcap.RawCapture> QueueRawCapture = new ConcurrentQueue<SharpPcap.RawCapture>();//(1024);
        private Queue<SharpPcap.RawCapture> QueueRawCapture = new Queue<SharpPcap.RawCapture>(102400);
        //private BackgroundWorker backProcess = new BackgroundWorker();
        private Dictionary<string, Func<string>> NetInformation = new Dictionary<string, Func<string>>(1024);
        
        protected override void OnActivated(EventArgs e)

        {
            base.OnActivated(e);
        }

        protected override void OnDeactivated(EventArgs e)
        {
            base.OnDeactivated(e);
        }

        protected override void OnExit(ExitEventArgs e)
        {
            StopRawCapturing();

            if (WebCore.IsInitialized)
                WebCore.Shutdown();

            base.OnExit(e);
        }

        protected override void OnFragmentNavigation(System.Windows.Navigation.FragmentNavigationEventArgs e)
        {
            base.OnFragmentNavigation(e);
        }

        protected override void OnLoadCompleted(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnLoadCompleted(e);
        }

        protected override void OnNavigated(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigated(e);
        }

        protected override void OnNavigating(System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            base.OnNavigating(e);
        }

        protected override void OnNavigationFailed(System.Windows.Navigation.NavigationFailedEventArgs e)
        {
            base.OnNavigationFailed(e);
        }

        protected override void OnNavigationProgress(System.Windows.Navigation.NavigationProgressEventArgs e)
        {
            base.OnNavigationProgress(e);
        }

        protected override void OnNavigationStopped(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigationStopped(e);
        }

        protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
        {
            base.OnSessionEnding(e);
        }

        protected override void OnStartup(StartupEventArgs e)
        {
            if (!WebCore.IsInitialized)
            {
                WebCore.Initialize(new WebConfig()
                {
                    HomeURL = "http://www.awesomium.com".ToUri(),
                    LogPath = @".\starter.log",
                    LogLevel = LogLevel.Verbose
                });
            }

            base.OnStartup(e);

            //webClient.Proxy = null;
            //webClient.DownloadDataCompleted += webClient_DownloadDataCompleted;
            //webClient.DownloadFileCompleted += webClient_DownloadFileCompleted;
            //webClient.DownloadProgressChanged += webClient_DownloadProgressChanged;
            //webClient.DownloadStringCompleted += webClient_DownloadStringCompleted;
            //webClient.OpenReadCompleted += webClient_OpenReadCompleted;
            //webClient.OpenWriteCompleted += webClient_OpenWriteCompleted;
            //webClient.UploadDataCompleted += webClient_UploadDataCompleted;
            //webClient.UploadFileCompleted += webClient_UploadFileCompleted;
            //webClient.UploadProgressChanged += webClient_UploadProgressChanged;
            //webClient.UploadStringCompleted += webClient_UploadStringCompleted;
            //webClient.UploadValuesCompleted += webClient_UploadValuesCompleted;

            //backProcess.WorkerSupportsCancellation = true;
            //backProcess.DoWork += backProcess_DoWorkSaveToFile;
            //backProcess.RunWorkerCompleted += backProcess_RunWorkerCompleted;

            NetInformation.Add("System.Environment", SystemEnvironment);

            Task.Run(() => 
            {
                while (true)
                {
                    GatteringAddressInformation();
                    Task.Delay(TimeSpan.FromSeconds(3.0)).Wait();
                }
            });

            //Task.Run(() =>
            //    {
            //        //IPAddress addr =null;
            //        while (true)
            //        {
            //            Task.Delay(TimeSpan.FromSeconds(10.0)).Wait();
            //            GatteringRouteInformation();
            //            //lock (RemoteHosts)
            //            //{
            //            //    if (RemoteHosts.Count < 1) continue;

            //            //    List<TracertEntry> list = null;
            //            //    if (!RemoteHosts.TryGetValue(addr, out list))
            //            //    {
            //            //        addr = RemoteHosts.Keys.
            //            //    }
            //            //}
            //        }
            //    });

            StartRawCapturing();
        }

        private void GatteringRouteInformation()
        {
            lock (RemoteHosts)
            {
                Parallel.ForEach<KeyValuePair<IPAddress, List<TracertEntry>>>(RemoteHosts, (addr) =>
                    {
                        addr.Value.Clear();
                        addr.Value.AddRange(Tracert(addr.Key.ToString(), 32, 10000));
                    });
            }
        }

        private Dictionary<IPAddress, List<TracertEntry>> RemoteHosts = new Dictionary<IPAddress, List<TracertEntry>>();

        public async Task<Dictionary<IPAddress, List<TracertEntry>>> GetRemoteHostRouteInfo()
        {
            return await Task.Run<Dictionary<IPAddress, List<TracertEntry>>>(() =>
                {
                    Dictionary<IPAddress, List<TracertEntry>> result = new Dictionary<IPAddress, List<TracertEntry>>(RemoteHosts.Count);
                    lock (RemoteHosts)
                    {
                        foreach (KeyValuePair<IPAddress, List<TracertEntry>> pair in RemoteHosts)
                        {
                            result.Add(new IPAddress(pair.Key.GetAddressBytes()), new List<TracertEntry>(pair.Value));
                        }
                    }
                    return result;
                });
        }

        private void GatteringAddressInformation()
        {
            IPHostEntry localhost = Dns.GetHostEntry(Dns.GetHostName());

            IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();

            //IPEndPoint[] endPoints = ipProperties.GetActiveTcpListeners();
            TcpConnectionInformation[] tcpConnections = ipProperties.GetActiveTcpConnections();

            //lock (RemoteHosts)
            {
                List<IPAddress> listNew = new List<IPAddress>();

                foreach (TcpConnectionInformation info in tcpConnections)
                {
                    if (info.RemoteEndPoint.Address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) continue;

                    //if (localhost.AddressList.Any((a) => { return a == info.RemoteEndPoint.Address; })) continue;

                    List<TracertEntry> v = null;

                    lock (RemoteHosts)
                    {
                        if (RemoteHosts.TryGetValue(info.RemoteEndPoint.Address, out v)) continue;
                        if (listNew.IndexOf(info.RemoteEndPoint.Address) < 0)
                        {
                            listNew.Add(info.RemoteEndPoint.Address);
                        }
                    }

                }

                //Parallel.ForEach(listNew, (addr) =>
                foreach (IPAddress addr in listNew)
                {
                    Task.Run(() =>
                        {
                            List<TracertEntry> v = new List<TracertEntry>(Tracert(addr.ToString(), 32, 10000));
                            lock (RemoteHosts)
                            //if (!RemoteHosts.TryGetValue(info.RemoteEndPoint.Address, out v))
                            {
                                RemoteHosts.Add(addr, v);//new List<TracertEntry>());
                            }
                        });
                }
            }
        }

        /// <summary>
        /// Traces the route which data have to travel through in order to reach an IP address.
        /// </summary>
        /// <param name="ipAddress">The IP address of the destination.</param>
        /// <param name="maxHops">Max hops to be returned.</param>
        public IEnumerable<TracertEntry> Tracert(string ipAddress, int maxHops, int timeout)
        {
            IPAddress address;

            // Ensure that the argument address is valid.
            if (!IPAddress.TryParse(ipAddress, out address))
                throw new ArgumentException(string.Format("{0} is not a valid IP address.", ipAddress));

            // Max hops should be at least one or else there won't be any data to return.
            if (maxHops < 1)
                throw new ArgumentException("Max hops can't be lower than 1.");

            // Ensure that the timeout is not set to 0 or a negative number.
            if (timeout < 1)
                throw new ArgumentException("Timeout value must be higher than 0.");


            Ping ping = new Ping();
            PingOptions pingOptions = new PingOptions(1, true);
            Stopwatch pingReplyTime = new Stopwatch();
            PingReply reply;
            byte[] buffer = Encoding.ASCII.GetBytes("pongpongpongpongpongpongpongpong");
            
            do
            {
                pingReplyTime.Start();
                //reply = ping.Send(address, timeout, new byte[] { 0 }, pingOptions);
                reply = ping.Send(address, timeout, buffer, pingOptions);
                pingReplyTime.Stop();

                string hostname = string.Empty;
                if (reply.Address != null)
                {
                    try
                    {
                        //hostname = Dns.GetHostByAddress(reply.Address).HostName;    // Retrieve the hostname for the replied address.
                        hostname = Dns.GetHostEntry(reply.Address).HostName;    // Retrieve the hostname for the replied address.
                    }
                    catch (SocketException) { /* No host available for that address. */ }
                }

                // Return out TracertEntry object with all the information about the hop.
                yield return new TracertEntry()
                {
                    HopID = pingOptions.Ttl,
                    Address = reply.Address == null ? "N/A" : reply.Address.ToString(),
                    Hostname = hostname,
                    ReplyTime = pingReplyTime.ElapsedMilliseconds,
                    RoundtripTime = reply.RoundtripTime,
                    ReplyStatus = reply.Status
                };

                pingOptions.Ttl++;
                pingReplyTime.Reset();
            }
            while (reply.Status != IPStatus.Success && pingOptions.Ttl <= maxHops);
        }

        public class TracertEntry
        {
            /// <summary>
            /// The hop id. Represents the number of the hop.
            /// </summary>
            public int HopID { get; set; }

            /// <summary>
            /// The IP address.
            /// </summary>
            public string Address { get; set; }

            /// <summary>
            /// The hostname
            /// </summary>
            public string Hostname { get; set; }

            /// <summary>
            /// The reply time it took for the host to receive and reply to the request in milliseconds.
            /// </summary>
            public long ReplyTime { get; set; }

            public long RoundtripTime { get; set; }

            /// <summary>
            /// The reply status of the request.
            /// </summary>
            public IPStatus ReplyStatus { get; set; }

            public override string ToString()
            {
                return string.Format("{0} | {1} | {2}({3})",
                    HopID,
                    string.IsNullOrEmpty(Hostname) ? Address : Hostname + "[" + Address + "]",
                    ReplyStatus == IPStatus.TimedOut ? "Request Timed Out." : ReplyTime.ToString() + " ms",
                    RoundtripTime
                    );
            }
        }
        public Task<HuknowInfo> DownloadInfoTaskAsync(Uri uri)
        {
            return Task.Run<HuknowInfo>(async () =>
                {
                    if (uri.IsFile) { }
                    if (uri.IsUnc) { }
                    //QueueRawCapture = new Queue<SharpPcap.RawCapture>(1024);
                    //StartRawCapturing();
                    string s = await (new WebClient() { Proxy = null }).DownloadStringTaskAsync(uri);
                    //StopRawCapturing();

                    // store uri and result of request
                    string pathToSave = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"Huknow", DateTime.Now.ToString("yyyy-MM-dd-hh-mm-ss"));
                    Directory.CreateDirectory(pathToSave);
                    using (StreamWriter writer = File.CreateText(Path.Combine(pathToSave, "content.html")))
                    {
                        writer.Write(s);
                    }

                    //XmlSerializer xmlSer = new XmlSerializer(typeof(Uri));
                    BinaryFormatter binForm = new BinaryFormatter();
                    using (FileStream writer = File.Create(Path.Combine(pathToSave, "uri.bin")))
                    {
                        //writer.Write(uri);
                        //xmlSer.Serialize(writer, uri);
                        binForm.Serialize(writer, uri);
                    }

                    return new HuknowInfo() { Html = s };
                });
        }

        void webClient_UploadValuesCompleted(object sender, UploadValuesCompletedEventArgs e)
        {
            //
        }

        void webClient_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
        {
            //
        }

        void webClient_UploadProgressChanged(object sender, UploadProgressChangedEventArgs e)
        {
            //progressBar.Value = e.ProgressPercentage;
        }

        void webClient_UploadFileCompleted(object sender, UploadFileCompletedEventArgs e)
        {
            //
        }

        void webClient_UploadDataCompleted(object sender, UploadDataCompletedEventArgs e)
        {
            //
        }

        void webClient_OpenWriteCompleted(object sender, OpenWriteCompletedEventArgs e)
        {
            //
        }

        void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            //
        }

        void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            StopRawCapturing();
            //backProcess.RunWorkerAsync(QueueRawCapture);
            ResetWebClientStatus();
            //RenderWebPage(e.Result);
        }

        void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            //progressBar.Value = e.ProgressPercentage;
        }

        void webClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            //
        }

        void webClient_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
        {
            //
        }

        void backProcess_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //Type t = typeof(BackgroundWorker);
            //EventInfo evnt = t.GetEvent("DoWork");
            //evnt.
            //((DoWorkEventHandler)backProcess.DoWork).GetInvocationList();
        }

        void backProcess_DoWorkSaveToFile(object sender, DoWorkEventArgs e)
        {
            //PacketDotNet.Packet[] packets;
            //lock (QueueRawCapture)
            //{
            //    packets = QueueRawCapture.Select((x) => { return PacketDotNet.Packet.ParsePacket(x.LinkLayerType, x.Data); }).ToArray();
            //}

            IPHostEntry hostEntry = Dns.GetHostEntry("");//BaseUriPriorPage.Host);

            return;

            //using (StreamWriter writer = new StreamWriter(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), String.Format(@"{0}_{1}.log", DateTime.Now.ToString("yyyy-MM-dd-hh-mm-ss"), ""))))//BaseUriPriorPage.Host))))
            //{
            //    foreach (PacketDotNet.Packet p in packets)
            //    {
            //        var ipv4 = p.Extract(typeof(PacketDotNet.IPv4Packet));
            //        if (ipv4 != null)
            //        {
            //            if (hostEntry.AddressList.Any((x) => { return x.Equals(((PacketDotNet.IPv4Packet)ipv4).DestinationAddress) || x.Equals(((PacketDotNet.IPv4Packet)ipv4).SourceAddress); }))
            //            {
            //                writer.WriteLine();
            //                //writer.WriteLine(ipv4.ToString());
            //                writer.WriteLine(p.ToString());
            //            }
            //        }
            //    }
            //}
        }

        private void StopRawCapturing()
        {
            SharpPcap.CaptureDeviceList devices = SharpPcap.CaptureDeviceList.Instance;
            foreach (SharpPcap.ICaptureDevice d in devices)
            {
                if (d.Started)
                {
                    d.StopCapture();
                    d.Close();
                }
                d.OnPacketArrival -= d_OnPacketArrival;
            }
        }

        private void ResetWebClientStatus()
        {
            //CurrentWebClientStatus.Content = "...";
            //progressBar.Value = 0;
        }

        private void StartRawCapturing()
        {
            SharpPcap.CaptureDeviceList devices = SharpPcap.CaptureDeviceList.Instance;
            foreach (SharpPcap.ICaptureDevice d in devices)
            {
                d.OnPacketArrival += d_OnPacketArrival;
                d.Open();
                d.StartCapture();
            }
        }

        void d_OnPacketArrival(object sender, SharpPcap.CaptureEventArgs e)
        {
            lock (QueueRawCapture)
            {
                QueueRawCapture.Enqueue(e.Packet);

                //SharpPcap.RawCapture raw;
                while (QueueRawCapture.Count > 10240) QueueRawCapture.Dequeue();// .TryDequeue(out raw);
            }
        }

        internal void DoNewWindow()
        {
            new MainWindow().Show();
        }

        internal void DoNewTraceWindow()
        {
            new TraceWindow().Show();
        }

        public string TryInvokeNetInformation(string NameOfClass)
        {
            Func<string> func = null;
            if(NetInformation.TryGetValue(NameOfClass,out func))
            {
                return func();
            }
            return null;
        }

        internal string SystemEnvironment()
        {
            XDocument xdoc = new XDocument(
                new XElement("System.Environment",
                    new XElement("CommandLine", System.Environment.CommandLine),
                    new XElement("CurrentDirectory", System.Environment.CurrentDirectory),
                    new XElement("CurrentManagedThreadId", System.Environment.CurrentManagedThreadId),
                    new XElement("Is64BitOperatingSystem", System.Environment.Is64BitOperatingSystem),
                    new XElement("Is64BitProcess", System.Environment.Is64BitProcess),
                    new XElement("MachineName", System.Environment.MachineName),
                    new XElement("OSVersion", System.Environment.OSVersion),
                    new XElement("ProcessorCount", System.Environment.ProcessorCount),
                    new XElement("SystemDirectory", System.Environment.SystemDirectory),
                    new XElement("SystemPageSize", System.Environment.SystemPageSize),
                    new XElement("UserDomainName", System.Environment.UserDomainName),
                    new XElement("Version", System.Environment.Version),
                    new XElement("WorkingSet", System.Environment.WorkingSet),
                    new XElement("GetFolderPath", new XAttribute("SpecialFolder", "AdminTools"), System.Environment.GetFolderPath(Environment.SpecialFolder.AdminTools)),
                    new XElement("GetFolderPath", new XAttribute("SpecialFolder", "ApplicationData"), System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)),
                    new XElement("GetFolderPath", new XAttribute("SpecialFolder", "CDBurning"), System.Environment.GetFolderPath(Environment.SpecialFolder.CDBurning)),
                    new XElement("GetFolderPath", new XAttribute("SpecialFolder", "Windows"), System.Environment.GetFolderPath(Environment.SpecialFolder.Windows)),
                    new XElement("GetLogicalDrives", System.Environment.GetLogicalDrives())
                    ));
            return xdoc.ToString(SaveOptions.None);
        }
    }

    public class HuknowInfo
    {
        public string Html { get; set; }
    }

    public enum KindOfAddress
    {
        Web,
        File,
        Net
    }
}
